#region

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;

#endregion

namespace MediaController.Extractor
{
    public class ArchiveCollection
    {
        private delegate void DirtyHandler(object sender);

        #region Data Members

        private event DirtyHandler Dirty;
        private readonly List<Archive> _archives = new List<Archive>();
        private readonly Statistics _statistics = new Statistics("Total");
        private bool _appendPrefix;
        private bool _appendSourceName = true;
        private int _archivesCount;
        private bool _cancel;
        private DirectoryInfo _destination;
        private int _maxDepth = 2;
        private DirectoryInfo _source;
        private string _extraContent = string.Empty;

        #endregion

        internal string Name
        {
            get { return Source.Name; }
        }

        public bool AppendArchivePrefix
        {
            get { return _appendPrefix; }
            set { _appendPrefix = value; }
        }

        public bool AppendSourceName
        {
            get { return _appendSourceName; }
            set { _appendSourceName = value; }
        }

        public List<Archive> Archives
        {
            get { return _archives; }
        }

        public int ArchivesCount
        {
            get { return _archivesCount; }
        }

        public DirectoryInfo Destination
        {
            get { return _destination; }
            set
            {
                if (((_destination == null) || (value == null)) || (!PathUtils.Equals(value, _destination)))
                {
                    _destination = value;
                    InvokeDestinationChanged(_destination);
                }
            }
        }

        public string ExtraContent
        {
            get { return _extraContent; }
            set { _extraContent = value ?? string.Empty; }
        }

        public int MaxDepth
        {
            get { return _maxDepth; }
            set
            {
                _maxDepth = value;
                if (Dirty != null)
                {
                    Dirty(this);
                }
            }
        }

        public DirectoryInfo Source
        {
            get { return _source; }
            set
            {
                if (((_source == null) || (value == null)) || (!PathUtils.Equals(value, _source)))
                {
                    _source = value;
                    InvokeDirty();
                }
            }
        }

        public Statistics Statistics
        {
            get { return _statistics; }
        }

        public event ExtractionProgressHandler ExtractionProgress;

        public event ArchiveFoundHandler ArchiveFound;

        public event ArchiveProcessedHandler ArchiveProcessed;

        public event ContentChangedHandler ContentChanged;

        public event DestinationChangedHandler DestinationChanged;

        public event EnteringDirectoryHandler EnteringDirectory;

        public event ExtraContentErrorHandler ExtraContentError;

        public event LeavingDirectoryHandler LeavingDirectory;

        public event ProcessEndedHandler ProcessEnded;

        public event NewArchiveHandler ProcessNextArchive;

        public event ProcessStartedHandler ProcessStarted;

        public event Action<object, EventArgs> DoneSearching;

        public ArchiveCollection()
        {
            Dirty = (DirtyHandler)Delegate.Combine(Dirty, new DirtyHandler(ArchiveCollectionDirty));
        }

        public void AsyncProcess()
        {
            var thread = new Thread(Process);
            thread.Name = Name;
            thread.Start();
        }

        public void Process()
        {
            if (Destination == null)
            {
                throw new NullReferenceException("No destination set!");
            }
            _archivesCount = Archives.Count;
            if (ProcessStarted != null)
            {
                Statistics.Begin();
            }
            InvokeProcessStarted();
            try
            {
                foreach (var archive in Archives)
                {
                    //if (selected.Contains(archive) && !_cancel)
                    {
                        Statistics.InitSplit(archive.ToString());
                        InvokeProcessNextArchive(archive);
                        try
                        {
                            DirectoryInfo dest = _destination;
                            if (_appendSourceName)
                            {
                                dest = new DirectoryInfo(Path.Combine(dest.FullName, _source.Name));
                            }
                            if (_appendPrefix)
                            {
                                dest = new DirectoryInfo(Path.Combine(dest.FullName, GetArchivePrefix(archive)));
                            }
                            if (!dest.Exists)
                            {
                                dest.Create();
                            }
                            if (ExtraContent != string.Empty)
                            {
                                foreach (var str in ExtraContent.Split(new[] { ' ' }))
                                {
                                    foreach (var info2 in archive.Directory.GetFiles(str))
                                    {
                                        string path = Path.Combine(dest.FullName, info2.Name);
                                        if (File.Exists(path))
                                        {
                                            goto Label_01AC;
                                        }
                                    Label_0167:
                                        try
                                        {
                                            info2.CopyTo(path, true);
                                        }
                                        catch (Exception exception)
                                        {
                                            bool flag = true;
                                            if (ExtraContentError != null)
                                            {
                                                var e = new ExtraContentErrorEventArgs(exception);
                                                InvokeExtraContentError(e);
                                                flag = !e.ContinueOperation;
                                            }
                                            if (!flag)
                                            {
                                                goto Label_0167;
                                            }
                                        }
                                    Label_01AC:
                                        ;
                                    }
                                }
                            }
                            archive.Unpack(dest.FullName);
                            continue;
                        }
                        finally
                        {
                            InvokeArchiveProcessed(archive);
                        }
                    }
                }
            }
            finally
            {
                InvokeProcessEnded();
            }
        }

        public void Stop()
        {
            _cancel = true;
        }

        #region Private Methods

        private void FindArchivies(DirectoryInfo d, Regex pat, int maxDepth)
        {
            InvokeEnteringDirectory(d);
            try
            {
                var list = new List<Archive>();
                FileInfo[] files = d.GetFiles();
                Array.Sort(files, (f1, f2) => f1.Name.CompareTo(f2.Name));
                foreach (var info in files)
                {
                    if (pat.IsMatch(info.Name))
                    {
                        bool flag = true;
                        foreach (var archive in list)
                        {
                            if (archive.Contains(info))
                            {
                                flag = false;
                                break;
                            }
                        }
                        if (flag)
                        {
                            var item = new Archive(_source, info);
                            item.Unrar.ExtractionProgress += UnrarExtractionProgress;
                            list.Add(item);
                            _archives.Add(item);
                            InvokeArchiveFound(item);
                        }
                    }
                }
                if (--maxDepth >= 0)
                {
                    foreach (var info2 in d.GetDirectories())
                    {
                        if (_cancel)
                        {
                            goto Label_0142;
                        }
                        FindArchivies(info2, pat, maxDepth);
                    }
                }
            }
            catch
            {
            }
        Label_0142:
            InvokeLeavingDirectory(d);
        }

        private void FindArchives()
        {
            _archives.Clear();
            if ((_source != null) && _source.Exists)
            {
                _cancel = false;
                var pat = new Regex(@"^.*\.rar$|^*.001", RegexOptions.IgnoreCase);
                FindArchivies(_source, pat, _maxDepth);
                InvokeDoneSearching();
            }
        }

        private string GetArchivePrefix(Archive a)
        {
            if (a.File.Directory != null)
            {
                if (a.File.Directory.FullName.Length <= Source.FullName.Length)
                {
                    return "";
                }
                return a.File.Directory.FullName.Substring(Source.FullName.Length + 1);
            }
            return null;
        }

        private void UnrarExtractionProgress(object sender, ExtractionProgressEventArgs e)
        {
            //InvokeExtractionProgress(e);
            Statistics.AddSample(e);
        }

        private void ArchiveCollectionDirty(object sender)
        {
            _cancel = true;
            InvokeContentChanged();

            FindArchives();
        }

        #region Event Invoker

        protected void InvokeDoneSearching()
        {
            Action<object, EventArgs> handler = DoneSearching;
            if (handler != null) handler(this, new EventArgs());
        }

        public void InvokeExtractionProgress(ExtractionProgressEventArgs e)
        {
            ExtractionProgressHandler handler = ExtractionProgress;
            if (handler != null) handler(this, e);
        }

        private void InvokeArchiveFound(Archive a)
        {
            ArchiveFoundHandler found = ArchiveFound;
            if (found != null) found(this, a);
        }

        private void InvokeArchiveProcessed(Archive a)
        {
            ArchiveProcessedHandler processed = ArchiveProcessed;
            if (processed != null) processed(this, a);
        }

        private void InvokeContentChanged()
        {
            ContentChangedHandler changed = ContentChanged;
            if (changed != null) changed(this);
        }

        private void InvokeDestinationChanged(DirectoryInfo newDestination)
        {
            DestinationChangedHandler changed = DestinationChanged;
            if (changed != null) changed(this, newDestination);
        }

        private void InvokeDirty()
        {
            DirtyHandler dirty = Dirty;
            if (dirty != null) dirty(this);
        }

        private void InvokeEnteringDirectory(DirectoryInfo d)
        {
            EnteringDirectoryHandler directory = EnteringDirectory;
            if (directory != null) directory(this, d);
        }

        private void InvokeExtraContentError(ExtraContentErrorEventArgs e)
        {
            ExtraContentErrorHandler error = ExtraContentError;
            if (error != null) error(this, e);
        }

        private void InvokeLeavingDirectory(DirectoryInfo d)
        {
            LeavingDirectoryHandler directory = LeavingDirectory;
            if (directory != null) directory(this, d);
        }

        private void InvokeProcessEnded()
        {
            ProcessEndedHandler ended = ProcessEnded;
            if (ended != null) ended(this);
        }

        private void InvokeProcessNextArchive(Archive a)
        {
            NewArchiveHandler archive = ProcessNextArchive;
            if (archive != null) archive(this, a);
        }

        private void InvokeProcessStarted()
        {
            ProcessStartedHandler started = ProcessStarted;
            if (started != null) started(this);
        }

        #endregion

        #endregion
    }

}